// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.

#include <sstream>
#include <utility>

#include "cangjie/AST/Node.h"
#include "cangjie/AST/Match.h"

#include "PackageExpression.h"

#include "ImportInfo.h"

using Cangjie::AST::ASTKind;
using Cangjie::AST::ImportKind;
using Cangjie::AST::Node;

using namespace CangjieMigrator;

ImportInfo::ImportInfo(std::string package, std::string modifier, const PositionRange &positionRange)
    : package(std::move(package)), modifier(std::move(modifier)), positionRange(positionRange)
{}

ImportIdentifierInfo::ImportIdentifierInfo(
    std::string package, std::vector<std::string> identifiers, std::string modifier, const PositionRange &positionRange)
    : ImportInfo(std::move(package), std::move(modifier), positionRange), identifiers(std::move(identifiers))
{}

ImportIdentifierInfo::ImportIdentifierInfo(
    std::string package, std::string identifier, std::string modifier, const PositionRange &positionRange)
    : ImportInfo(std::move(package), std::move(modifier), positionRange), identifiers({std::move(identifier)})
{}

ImportIdentifierInfo::ImportIdentifierInfo(const PackageExpression &expression)
    : ImportIdentifierInfo(expression.package, expression.identifier)
{}

ImportAliasInfo::ImportAliasInfo(std::string package, std::string identifier, std::string modifier, std::string alias,
    const PositionRange &positionRange)
    : ImportInfo(std::move(package), std::move(modifier), positionRange), identifier(std::move(identifier)),
      alias(std::move(alias))
{}

ImportAllInfo::ImportAllInfo(std::string package, std::string modifier, const PositionRange &positionRange)
    : ImportInfo(std::move(package), std::move(modifier), positionRange)
{}

bool ImportInfo::TryModifyOrRemove(const Rules::ImportRule &rule)
{
    return false;
}

static bool StringStartsWith(const std::string &a, const std::string &b)
{
    return a.compare(0, b.length(), b) == 0;
}

bool ImportInfo::TryReplacePackage(const std::string &source, const std::string &target)
{
    if (!StringStartsWith(package, source)) {
        return false;
    }
    const auto next = package.substr(source.length());
    if (!next.empty()) {
        const auto a = next[0];
        if (('a' <= a && a <= 'z') || ('A' <= a && a <= 'Z') || ('0' <= a && a <= '9') || a == '_') {
            return false;
        }
    }
    package = target + next;
    return true;
}

bool ImportInfo::IsImplicit() const
{
    return false;
}

bool ImportIdentifierInfo::TryModifyOrRemove(const Rules::ImportRule &rule)
{
    if (rule.CanUpdate()) {
        const auto &add = rule.GetAdd()[0];
        const auto &remove = rule.GetRemove()[0];
        if (remove.package != package) {
            return false;
        }
        if (add.package == package) {
            if (std::find(identifiers.begin(), identifiers.end(), remove.identifier) != identifiers.end()) {
                identifiers.erase(
                    std::remove(identifiers.begin(), identifiers.end(), remove.identifier), identifiers.end());
                identifiers.push_back(add.identifier);
            }
            return false;
        }
    }

    for (auto &expression : rule.GetRemove()) {
        if (expression.package == package) {
            identifiers.erase(std::remove_if(identifiers.begin(),
                identifiers.end(),
                [&expression](const std::string &e) { return e == expression.identifier; }),
                identifiers.end());
        }
    }
    return identifiers.empty();
}

bool ImportAliasInfo::TryModifyOrRemove(const Rules::ImportRule &rule)
{
    if (rule.CanUpdate()) {
        auto &remove = rule.GetRemove()[0];
        auto &add = rule.GetAdd()[0];
        if (remove.package == package && remove.identifier == identifier) {
            package = add.package;
            identifier = add.identifier;
        }
        return false;
    }

    const auto &removeList = rule.GetRemove();
    if (removeList.size() > 1) {
        for (auto &remove : removeList) {
            if (remove.package == package && remove.identifier == identifier) {
                return true;
            }
        }
    }
    return false;
}

ImportInfo::Type ImportIdentifierInfo::GetType() const
{
    return Type::IMPORT_IDENTIFIER;
}

ImportInfo::Type ImportAliasInfo::GetType() const
{
    return Type::IMPORT_ALIAS;
}

ImportInfo::Type ImportAllInfo::GetType() const
{
    return Type::IMPORT_ALL;
}

bool ImportAllInfo::IsImplicit() const
{
    return package == "std.core";
}

void ImportIdentifierInfo::ExportTo(std::vector<PackageExpression> &v) const
{
    for (auto &identifier : identifiers) {
        v.emplace_back(PackageExpression(package, identifier));
    }
}

void ImportAliasInfo::ExportTo(std::vector<PackageExpression> &v) const
{
    v.emplace_back(package, identifier);
}

void ImportAllInfo::ExportTo(std::vector<PackageExpression> &v) const
{
    v.emplace_back(package, "*");
}

std::string ImportIdentifierInfo::ToString() const
{
    if (identifiers.empty()) {
        return "";
    }
    std::string packagePrefix;
    if (package.empty()) {
        packagePrefix = package;
    } else {
        packagePrefix = package + ".";
    }
    if (identifiers.size() == 1) {
        return GenerateModifier() + "import " + packagePrefix + identifiers[0];
    }
    auto result = GenerateModifier() + "import " + packagePrefix + "{";
    bool isBegin = true;
    for (auto &identifier : identifiers) {
        if (isBegin) {
            result += identifier;
            isBegin = false;
        } else {
            result += ", " + identifier;
        }
    }
    result += "}";
    return result;
}

std::string ImportAliasInfo::ToString() const
{
    std::string packagePrefix;
    if (package.empty()) {
        packagePrefix = package;
    } else {
        packagePrefix = package + ".";
    }
    return GenerateModifier() + "import " + packagePrefix + identifier + " as " + alias;
}

std::string ImportAllInfo::ToString() const
{
    return GenerateModifier() + "import " + package + ".*";
}

static std::string Join(const std::vector<std::string> &strings, const char connector)
{
    std::stringstream result;
    for (auto it = strings.begin(); it != strings.end(); ++it) {
        result << *it;
        if (it != strings.end() - 1) {
            result << connector;
        }
    }
    return result.str();
}

static std::string Join(const std::vector<std::string> &strings, const std::string &connector)
{
    std::stringstream result;
    for (auto it = strings.begin(); it != strings.end(); ++it) {
        result << *it;
        if (it != strings.end() - 1) {
            result << connector;
        }
    }
    return result.str();
}

std::unique_ptr<ImportInfo> ImportInfo::Build(const Cangjie::AST::ImportSpec &node, const PositionRange &range)
{
    switch (node.content.kind) {
        case ImportKind::IMPORT_SINGLE:
            return ImportIdentifierInfo::BuildSingle(node, range);
        case ImportKind::IMPORT_ALIAS:
            return ImportAliasInfo::Build(node, range);
        case ImportKind::IMPORT_ALL:
            return ImportAllInfo::Build(node, range);
        case ImportKind::IMPORT_MULTI:
            return ImportIdentifierInfo::BuildMulti(node, range);
        default:
            return nullptr;
    }
}

std::string ImportInfo::GenerateModifier() const
{
    if (modifier.empty()) {
        return "";
    } else {
        return modifier + ' ';
    }
}

std::unique_ptr<ImportInfo> ImportIdentifierInfo::BuildSingle(
    const Cangjie::AST::ImportSpec &node, const PositionRange &range)
{
    const auto &content = node.content;
    auto modifier = node.modifier != nullptr ? node.modifier->ToString() : std::string();
    auto packageName = Join(content.prefixPaths, '.');
    return std::make_unique<ImportIdentifierInfo>(packageName, content.identifier.Val(), modifier, range);
}

std::unique_ptr<ImportInfo> ImportIdentifierInfo::BuildMulti(
    const Cangjie::AST::ImportSpec &node, const PositionRange &range)
{
    const auto &content = node.content;
    auto modifier = node.modifier != nullptr ? node.modifier->ToString() : std::string();
    auto packageName = Join(content.prefixPaths, '.');
    std::vector<std::string> identifiers;
    for (const auto &item : content.items) {
        identifiers.emplace_back(item.identifier.Val());
    }
    return std::make_unique<ImportIdentifierInfo>(packageName, identifiers, modifier, range);
}

std::unique_ptr<ImportInfo> ImportAliasInfo::Build(
    const Cangjie::AST::ImportSpec &node, const PositionRange &range)
{
    const auto &content = node.content;
    auto modifier = node.modifier != nullptr ? node.modifier->ToString() : std::string();
    const auto &alias = content.aliasName;
    auto packageName = Join(content.prefixPaths, '.');
    return std::make_unique<ImportAliasInfo>(packageName, content.identifier.Val(), modifier, alias, range);
}

std::unique_ptr<ImportInfo> ImportAllInfo::Build(
    const Cangjie::AST::ImportSpec &node, const PositionRange &range)
{
    const auto &content = node.content;
    auto modifier = node.modifier != nullptr ? node.modifier->ToString() : std::string();
    auto packageName = Join(content.prefixPaths, '.');
    return std::make_unique<ImportAllInfo>(packageName, modifier, range);
}
